#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <regex.h>

#include "../util.h"
#include "probe.h"
#include "../error.h"

#define MATCH_PAGE_REGEX "^<script>top\\.self\\.location\\.href=" \
    "'http://(.+?)/eportal/index.jsp\\?(.+?)'</script>"

typedef enum {
    ACTION_MATCHED,
    ACTION_MATCHED_NOTHING,
    ACTION_MATCH_FAILURE
} action_match_login_status;

typedef struct {
    action_match_login_status status;
    char* host;
    char* search;
} action_match_login_result;

/*
*   取src的子字符串[start, end]到dest。
*   使用malloc给dest分配内存。
*/
#define COPY_STRING(dest, src, start, end)                                      \
    {                                                                           \
        (dest) = (char*) malloc(end - start + 1);                               \
        memcpy((dest), src + start, end - start);                              \
        (dest)[end - start] = 0;                                                \
    }

action_match_login_result match_login_page(void* data) {
    action_match_login_result result = {
        ACTION_MATCH_FAILURE,
        NULL,
        NULL
    };
    regex_t regex;
    if (regcomp(&regex, MATCH_PAGE_REGEX, REG_EXTENDED) != 0) {
        error(0, 0, "regcomp failed");
        result.status = ACTION_MATCH_FAILURE;
        return result;
    }
    
    regmatch_t matches[3];
    // CURL_EASY_SETUP ensures the data is end with '\0'
    int match_result = regexec(&regex, (char*) data, 3, matches, 0);
    if (match_result == 0) {
        // matched
        COPY_STRING(result.host, data,
            matches[1].rm_so, matches[1].rm_eo);
        COPY_STRING(result.search, data,
            matches[2].rm_so, matches[2].rm_eo);
        result.status = ACTION_MATCHED;
        return result;
    } else {
        result.status = ACTION_MATCHED_NOTHING;
        return result;
    }
}

action_probe_result action_probe(
    const char* bind_interface,
    const char* probe_address,
    action_probe_mode probe_mode,
    const char* probe_content,
    int probe_status
) {
    assert(probe_address != NULL);
    action_probe_result result = {
        ACTION_PROBEING_FAILURE,
        NULL,
        NULL
    };
    CURL_EASY_SETUP({
        // Setup action
    }, {
        // Perform action
        size_t probe_content_size;
        switch (probe_mode) {
            case PROBE_MODE_STATUS:
                if (probe_status == status) {
                    result.status = ACTION_PROBED_SUCCESSFULLY;
                    return result;
                }
                break;
            case PROBE_MODE_CONTENT:
                assert(probe_content != NULL);
                probe_content_size = strlen(probe_content);
                if (chunk.size != probe_content_size) {
                    break;
                }
                if (memcmp(chunk.memory, probe_content, chunk.size) != 0) {
                    break;
                }
                result.status = ACTION_PROBED_SUCCESSFULLY;
                return result;
                break;
            case PROBE_MODE_STATUS_AND_CONTENT:
                assert(probe_content != NULL);
                if (probe_status != status) {
                    break;
                }
                probe_content_size = strlen(probe_content);
                if (chunk.size != probe_content_size) {
                    break;
                }
                if (memcmp(chunk.memory, probe_content, chunk.size) != 0) {
                    break;
                }
                result.status = ACTION_PROBED_SUCCESSFULLY;
                return result;
                break;
            default:
                assert(0);
                exit(1);
                break;
        }
        
        // Didn't matched, try to match login page
        action_match_login_result login_match = match_login_page(chunk.memory);
        if (login_match.status == ACTION_MATCH_FAILURE) {
            result.status = ACTION_PROBEING_FAILURE;
            return result;
        } else if (login_match.status == ACTION_MATCHED) {
            result.status = ACTION_PROBED_LOGIN_PAGE;
            result.host = login_match.host;
            result.search = login_match.search;
            return result;
        }
        
        // Nothing is matched
        result.status = ACTION_PROBED_NOTHING;
        return result;
    }, {
        // Failure action
        result.status = ACTION_PROBEING_FAILURE;
        return result;
    }, probe_address, bind_interface, 1024);
}

void action_probe_result_free(action_probe_result* result) {
    if (result -> host != NULL) {
        free((char*) result -> host);
    }
    if (result -> search != NULL) {
        free((char*) result -> search);
    }
}
